"""
Simple jython template that generates event manager.
That's a preliminary job. later 
    use a java code (maybe jythonc this)
    use an on the fly code generation ( using ASM )

"""
from os import makedirs,open
from java.io import File, FileWriter
from java.awt.event import ActionListener



def generate(eventlistener, methods, methods_definitions, package='com.googlecode.neoevents'):
    """
    Simple code generation. 100% pur python, (no java introspection needed
    :param eventlistener: , the listener name
    :param methods: a list of method names
    :param methods_definitions: a list of string for java sources method definition
    :param package: the actual top package name for events
    """
    
    #get the class name, and the actual package by merging it
    actualpackage,name=eventlistener.rsplit('.', 1) # a.b.c.d.e turns into [a.b.c.d, e]
    actualpackage=package+"."+actualpackage
    
    #get the list of methods
    
    ex_method = methods[0] #method for the doc example
    #lsit of event method names in a <ul> for doc
    li_methodlist = "<ul>"+"".join( [ '<li>%(method)s</li>\n'%locals() for method in methods] )+ "</ul>"
    
    #methods definition
    def_methods = "\n".join(methods_definitions)
    
    #annotation declaration passed to the constructor
    decl_annotation_method="".join([ "            ,%(method)s.class\n"%locals() for method in methods])
    
    
    return """
package %(actualpackage)s;

//GENERATED DO NOT EDIT

import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import com.googlecode.neoevents.AbstractEvent;

/**
 *  <p>This is a transparent {@link %(eventlistener)s} that uses annotations to convert any objects into
 *  an %(name)s. </p>
 * 
 * Annotating methods of any object with the following annotations: 
 *
 * %(li_methodlist)s
 *
 * will call those methods when the corresponding events is fired.
 * 
 * example
 * <p>
 * <pre>
 * public class MyController {
 *     
 *     ...
 *     
 *     Object source;
 * 
 *     ...
 * 
 *     source.add%(name)s(new %(name)s(this) );
 *     
 *     ...    
 *     
 *     &#64;%(name)s.%(ex_method)s
 *     protected void go(){
 *     System.out.println("go! go! go!"); // called when source will trigger the %(name)s.%(ex_method)s event
 *     }
 * 
 * }
 * 
 * methods can be either:
 * <ul>
 * <li>without parameters</li>
 * <li>without one parameter of any super type of the corresponding event type</li>
 * </ul>
 * 
 * </pre>
 * 
 * </p>
 * 
 *
 */
public class %(name)s extends AbstractEvent implements %(eventlistener)s{

    /**
     * @param target
     */
    public %(name)s(Object target) {
        super(target
                // for each method add the associated annotation
%(decl_annotation_method)s
                
        );
    }
    
    %(def_methods)s
    
    
}"""%locals()

def get_name(clazz):
    """
    due to jython bug 1002, need a workaround
    
    """
    try:
        return clazz.name #clazz.package.name+clazz.__name__
    except:
        return clazz.package.name+"."+clazz.__name__

def listeners(*packages):
    """
    introspect a list of packages and return class good candidate for listeners
    """
    java_event_listeners=[]
    for package in iter(packages):
        #get all objects from the module
        children =[]
        for name in dir(package):
            try:
                children.append(getattr(package, name))
            except:
                print "failed loading java class ", name, " Skipped"  
        
        #filter java packages
        java_packages = [p 
                        for p in children  
                        if type(p).__name__=="javapackage" ] 
        #for each package
        for p in java_packages:
            if p!=package:
                java_event_listeners+= listeners(p)  #recursively parse packages
        
        #then for this package gets all the classes
        java_classes = [clazz 
                        for clazz in children  
                        if isinstance(clazz, java.lang.Class) ]
        
        #and for each class
        for c in java_classes:
            try:
                #test some pattern matching
                #bug 1002 workaround, using __name__ gives the simple name, based on that simpleName, all xxListener are out of the bug
                if c.__name__.endswith('Listener') and java.util.EventListener.isAssignableFrom(c) and len(c.methods)>0 and c.isInterface():
                    for m in c.methods:
                        for t in m.parameterTypes:
                            pass #test that there is no fail
                    java_event_listeners.append(c)
            except Exception, e:
                print "skipped ",c, "due to ", e  #why them ? java.lang.Class, java.net.MulticastSocket
    return java_event_listeners


def java_generate_file(path, listener_class):
    """
    generates a single listener class into it's java file
    """
    code = java_generate(listener_class) #generate the code 
    if not code: return
    
    #then generate the file
    filename=path+"/"+get_name(listener_class).replace('.', '/')+".java"
    print filename
    
    f= File(filename)
    f.parentFile.mkdirs()
    w = FileWriter(f)
    try:
        w.write(code)
    finally:
        w.close()


def declare(type):
    if type.startswith('[') and type.endswith(';'):
        return declare(type[1:-1])+'[]'
    elif type.startswith('['):
        return declare(type[1:])+'[]'
    elif type=='Z': return 'boolean'
    elif type=='B': return 'byte'
    elif type=='C': return 'char'
    elif type=='S': return 'short'
    elif type=='I': return 'int'
    elif type=='J': return 'long'
    elif type=='F': return 'float'
    elif type=='D': return 'double'
    elif type=='I': return 'int'
    return type

        


def gen_method(eventlistener, method):
    """
    generate a single event method, that calls back at super.fire( annotation, objects... ).
    :param eventListener: simply the eventlistener name (for javadoc)
    :param method: the actual method to generate
    """
    
    name=method.name #the name of the method
    types=[declare(get_name(t)) for t in method.parameterTypes] # by introspecting, find the actual function signature.
             
    vars=["arg%s"%i for i,t in enumerate(types)] # generates a list variables based on their type name, lowercased, and skipping package name
    
    sep=", " 
    type_vars="".join([sep+t+" "+v for t,v in zip(types, vars) ])[len(sep):] #gen couple type var, type2 var2 etc... then skip the starting ','
    var_calls="".join([sep+v for v in vars])[len(sep):] # generate a standard calling interface, then remove the starting comma. 
    return """
    /** Annotation to mark a method as a target for {@link %(eventlistener)s.%(name)s()} calls  */
    @Retention(RetentionPolicy.RUNTIME) public static @interface %(name)s {}
    @Override public void %(name)s(%(type_vars)s) { fire(%(name)s.class, %(var_calls)s); } 
"""%locals()
    
    
def java_generate(listener_class):
    """
    fully generate the java code for the neoevent listener that matches this listener class.
    This method should, one day be cloned using asm (cloned because asm loses the comments, and the javadoc, but is very nice for on the fly generation)
    """
    try:
        eventlistener=get_name(listener_class) #actual listener name
        methods = [m.name for m in listener_class.methods] #actual method names
        methods_definitions= [gen_method(eventlistener, m) for m in listener_class.methods] #actual method definition names
        return generate(eventlistener, methods, methods_definitions)
    except Exception,e:
        print "failed to generate ", listener_class.canonicalName
        print e
    

if __name__ == '__main__':
    import javax,java
    
    listener_classes = listeners(java,javax)#get all the listeners
    #and generate them
    for listener in listener_classes:
        java_generate_file("./src/main/java/com/googlecode/neoevents", listener)
    print len(listener_classes), "files generated"
    
    
    
